package org.zhangchao.poi.excel.operate;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.ss.util.CellRangeAddress;
import org.zhangchao.poi.excel.common.template.ExcelTemplateParam;
import org.zhangchao.poi.excel.create.ExcelCreatable;
import org.zhangchao.poi.excel.interceptor.ModelData;

/**
 * 实现了基于模板导出Excel 模板定义标识： 要从哪一行那一列开始替换需要定义一个标识为data、
 * 如果要替换信息，需要传入一个Map，这个map中存储着要替换信息的值，在excel中通过#来开头、
 * 如果要设定相应的样式，可以在该行使用style完成设定，此时所有此行都使用该样式、
 * 如果使用defaultStyle作为表示，表示默认样式，如果没有defaultStyle使用data单元格样式作为默认样式
 * 
 * @author zhangchao
 * 
 */
public class ExcelTemplate {
	
	/** Workbook */
	private Workbook workbook;
	/** Sheet */
	private Sheet sheet;
	/** 模板参数 */
	private ExcelTemplateParam excelTemplateParam;
	/** 数据行标识 */
	public final static String DATA_LINE = "data";
	/** 默认样式标识 */
	public final static String DEFAULT_STYLE = "defaultStyle";
	/** 列样式标识 */
	public final static String STYLE = "style";
	/** 数据的初始化列数 */
	private int initColIndex;
	/** 数据的初始化行数 */
	private int initRowIndex;
	/** 当前列数 */
	private int curColIndex;
	/** 当前行数 */
	private int curRowIndex;
	/** 当前行对象 */
	private Row curRow;
	/** 序号的列 */
	private int serColIndex = -1;
	/** 最后一行的数据 */
	private int lastRowIndex;
	/** 默认样式*/
	private CellStyle defaultStyle;
	/** 默认行高 */
	private float rowHeight;
	/** 存储某一列所对于的样式*/
	private Map<Integer, CellStyle> styles;
	
	public ExcelTemplate() {
	}
	
	public ExcelTemplate(Workbook workbook) {
		this.workbook = workbook;
		initTemplate();
	}
	
	public ExcelTemplate(String path) {
		readTemplate(path);
	}

	public ExcelTemplate(File templateFile) {
		createTemplate(templateFile);
	}

	public ExcelTemplate(InputStream templateInputStream) {
		createTemplate(templateInputStream);
	}

	/**
	 * 以输入流方式读取模板
	 * @param is
	 * @return
	 */
	public ExcelTemplate readTemplate(InputStream is) {
		return createTemplate(is);
	}

	/**
	 * 以文件方式读取模板
	 * @param file
	 * @return
	 */
	public ExcelTemplate readTemplate(File file) {
		return createTemplate(file);
	}

	/**
	 * 以类路径方式读取模板
	 * @param path
	 * @return
	 */
	public ExcelTemplate readTemplateByClasspath(String path) {
		return readTemplate(ExcelTemplate.class.getResourceAsStream(path));
	}

	/**
	 * 以路径读取模板
	 * @param path
	 * @return
	 */
	public ExcelTemplate readTemplate(String path) {
		return readTemplate(new File(path));
	}

	/**
	 * 创建模板
	 * @param obj
	 * @return
	 */
	private ExcelTemplate createTemplate(Object obj) {
		try {
			if (obj == null)
				throw new RuntimeException("读取模板失败，请检查！");
			if (obj instanceof InputStream) {
				workbook = WorkbookFactory.create((InputStream) obj);
			} else if (obj instanceof File) {
				workbook = WorkbookFactory.create((File) obj);
			}
			initTemplate();
		} catch (InvalidFormatException e) {
			e.printStackTrace();
			throw new RuntimeException("读取模板格式有错，请检查！");
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException("读取模板不存在，请检查！");
		}
		return this;
	}

	/**
	 * 输出文件到指定路径
	 * @param filePath
	 */
	public void write(String filePath) {
		writeExcel(filePath);
	}

	/**
	 * 输出到指定文件
	 * @param file
	 */
	public void write(File file) {
		writeExcel(file);
	}

	/**
	 * 写入
	 * @param obj
	 */
	private void writeExcel(Object obj) {
		if (obj == null)
			return;
		FileOutputStream fos = null;
		try {
			if (obj instanceof String)
				fos = new FileOutputStream((String) obj);
			else if (obj instanceof File)
				fos = new FileOutputStream((File) obj);

			workbook.write(fos);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			throw new RuntimeException("找不到指定的路径");
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException("写入数据失败:" + e.getMessage());
		} finally {
			try {
				if (fos != null)
					fos.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 输出文件到输出流
	 * @param os
	 */
	public void wirte(OutputStream os) {
		try {
			workbook.write(os);
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException("写入流失败:" + e.getMessage());
		}
	}
	
	/**
	 * 创建
	 * @param ec
	 */
	public void create(ExcelCreatable ec) {
		int rowNum = 0;
		ModelData modelData = (ModelData)ec;
		List<?> data = modelData.prepareModelData();
		
		for (Row row : sheet) {
			if(rowNum == initRowIndex) {
				//
				if(ec instanceof ModelData) {
					if(null != data) {
						for (int i = 0; i < data.size(); i++) {
							ec.createRow(rowNum++);
						}
					}
				}
			}else {
				//
				Row newRow = ec.copyRow(rowNum++, row);
				for (Cell cell : row) {
					if (null != cell && (cell.getCellType() == Cell.CELL_TYPE_STRING)) {
						String cellValue = cell.getStringCellValue().trim();
						if (DEFAULT_STYLE.equals(cellValue)) {
							continue;
						}
					}
					ec.copyCell(newRow, cell.getColumnIndex(), cell);
				}
			}
		}
		//合并单元格
		ec.handelMergedRegion(getMergedRegion(data.size()));
	}
	
	/**
	 * 获取合并单元格列表
	 * @return
	 */
	private List<CellRangeAddress>  getMergedRegion(int dataNum) {
		List<CellRangeAddress> craList = new ArrayList<CellRangeAddress>();
		if(null != sheet) {
			// 获得一个 sheet 中合并单元格的数量
			int sheetmergerCount = sheet.getNumMergedRegions();
			// 遍历合并单元格
			for (int i = 0; i < sheetmergerCount; i++) {
				// 获得合并单元格加入list中
				CellRangeAddress ca = sheet.getMergedRegion(i);
				int firstRow = ca.getFirstRow();
				int lastRow = ca.getLastRow();
				if(firstRow > initRowIndex) {
					ca.setFirstRow(firstRow + dataNum - 1);
				}
				if(lastRow > initRowIndex) {
					ca.setLastRow(lastRow + dataNum - 1);
				}
				craList.add(ca);
			}
		}
		return craList;
	}
	
	/**
	 * 获取模板参数
	 * @return
	 */
	public ExcelTemplateParam getExcelTemplateaParam() {
		if(null == excelTemplateParam) {
			excelTemplateParam = new ExcelTemplateParam();
			excelTemplateParam.setDefaultStyle(defaultStyle);
			excelTemplateParam.setInitColIndex(initColIndex);
			excelTemplateParam.setRowHeight(rowHeight);
			excelTemplateParam.setStyles(styles);
		}
		return excelTemplateParam;
	}

	/**
	 * 创建行
	 */
	public Row createRow() {
		// 在新创建行时，原本的数据向后移动一行
		if (lastRowIndex > curRowIndex && curRowIndex != initRowIndex) {
			sheet.shiftRows(curRowIndex, lastRowIndex, 1, true, true);
			lastRowIndex++;
		}
		curRow = sheet.createRow(curRowIndex);
		curRow.setHeightInPoints(rowHeight);
		curRowIndex++;
		curColIndex = initColIndex;
		return curRow;
	}

	/**
	 * 设置单元样式
	 * @param cell
	 */
	private void setCellStyle(Cell cell) {
		if (styles == null)
			return;
		if (styles.containsKey(curColIndex)) {
			cell.setCellStyle(styles.get(curColIndex));
		} else {
			cell.setCellStyle(defaultStyle);
		}
	}

	/**
	 * 插入序号
	 */
	public void insertSerNum() {
		if (serColIndex == -1) {
			throw new RuntimeException("插入序号失败，请检查");
		}
		int index = 1;
		Row row = null;
		Cell cell = null;
		for (int i = initRowIndex; i < curRowIndex; i++) {
			row = sheet.getRow(i);
			cell = row.createCell(serColIndex);
			setCellStyle(cell);
			cell.setCellValue(index++);
		}
	}

	/**
	 * 设置以“#开头”标识的数据
	 * 
	 * @param datas
	 */
	public void setLableData(Map<String, String> datas) {
		if (datas == null)
			return;
		setLableDate(datas);
	}

	/**
	 * 根据配置文件，设置以“#开头”标识的数据
	 * 
	 * @param properties
	 */
	public void setLableData(Properties properties) {
		if (properties == null)
			return;
		setLableDate(properties);
	}

	/**
	 * 设置以“#开头”标识的数据
	 * 
	 * @param obj
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	private void setLableDate(Object obj) {
		if (obj == null)
			return;
		for (Row row : sheet) {
			for (Cell cell : row) {
				if (cell == null
						|| (cell.getCellType() != Cell.CELL_TYPE_STRING))
					continue;
				String cellValue = cell.getStringCellValue().trim();
				if (cellValue.startsWith("#")) {
					String key = cellValue.substring(1);
					if (((Map) obj).containsKey(key)) {
						if (obj instanceof Properties)
							cell.setCellValue(((Properties) obj)
									.getProperty(key));
						else {
							cell.setCellValue(((Map<String, String>) obj)
									.get(key));
						}
					}
				}
			}
		}
	}

	/**
	 * 初始化模板
	 */
	private void initTemplate() {
		sheet = workbook.getSheetAt(0);
		initConfigData();
		lastRowIndex = sheet.getLastRowNum();
		//curRow = sheet.createRow(curRowIndex);
	}

	/**
	 * 初始化模板数据信息
	 */
	private void initConfigData() {
		for (Row row : sheet) {
			for (Cell cell : row) {
				if (cell == null
						|| (cell.getCellType() != Cell.CELL_TYPE_STRING)) {
					continue;
				}
				String cellValue = cell.getStringCellValue().trim();
				if (DATA_LINE.equals(cellValue)) {
					initColIndex = cell.getColumnIndex();
					initRowIndex = row.getRowNum();
					curColIndex = initColIndex;
					curRowIndex = initRowIndex;
					if (defaultStyle == null) {
						defaultStyle = cell.getCellStyle();
						//row.removeCell(cell);
					}
					rowHeight = row.getHeightInPoints();
				} else if (DEFAULT_STYLE.equals(cellValue)) {
					defaultStyle = cell.getCellStyle();
				} else if (STYLE.equals(cellValue)) {
					if (styles == null) {
						styles = new HashMap<Integer, CellStyle>();
					}
					styles.put(cell.getColumnIndex(), cell.getCellStyle());
				}
			}
		}
	}
	
	

	/**
	 * 创建列
	 * 
	 * @param value
	 */
	public void createCell(String value) {
		Cell cel = curRow.createCell(curColIndex);
		setCellStyle(cel);
		cel.setCellValue(value);
		curColIndex++;
	}

	/**
	 * 创建列
	 * 
	 * @param value
	 */
	public void createCell(int value) {
		Cell cel = curRow.createCell(curColIndex);
		setCellStyle(cel);
		cel.setCellValue((int) value);
		curColIndex++;
	}

	/**
	 * 创建列
	 * 
	 * @param value
	 */
	public void createCell(Date value) {
		Cell cel = curRow.createCell(curColIndex);
		setCellStyle(cel);
		cel.setCellValue(value);
		curColIndex++;
	}

	/**
	 * 创建列
	 * 
	 * @param value
	 */
	public void createCell(double value) {
		Cell cel = curRow.createCell(curColIndex);
		setCellStyle(cel);
		cel.setCellValue(value);
		curColIndex++;
	}

	/**
	 * 创建列
	 * 
	 * @param value
	 */
	public void createCell(boolean value) {
		Cell cel = curRow.createCell(curColIndex);
		setCellStyle(cel);
		cel.setCellValue(value);
		curColIndex++;
	}

	/**
	 * 创建列
	 * 
	 * @param value
	 */
	public void createCell(Calendar value) {
		Cell cell = curRow.createCell(curColIndex);
		setCellStyle(cell);
		cell.setCellValue(value);
		curColIndex++;
	}

	public Sheet getSheet() {
		return sheet;
	}

	public void setSheet(Sheet sheet) {
		this.sheet = sheet;
	}

}
